/**
 * @Created Mar 29, 2011 4:05:29 PM
 * @author cry30
 */
package com.philip.journal.core.dao.spring.hibernate;

import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.NonUniqueResultException;
import org.hibernate.QueryException;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.HibernateCallback;

import com.philip.core.WarningType;
import com.philip.journal.core.Messages;
import com.philip.journal.core.bean.AbstractBean;
import com.philip.journal.core.dao.spring.hibernate.BaseDAOSpringHibernate.CriteriaType;
import com.philip.journal.core.exception.JournalException;

/**
 * Refactored out from {@link BaseDAOSpringHibernate#readObject(Map, String, CriteriaType)}.
 *
 * @param <T> return type. Can be entity or List.
 */
public class ReadObjectCallback1<T> implements HibernateCallback<T> {

    /** Parameter map for the internal hibernate call. */
    private final transient Map<String, Object> param;

    /** Method to invoke inside hibernate call. */
    private final transient String methodName;

    /** Criteria type. */
    private final transient CriteriaType criteriaType;

    /** Target entity class. */
    private final transient Class<? extends AbstractBean> targetClass;

    /**
     * Default parameterized constructor.
     *
     * @param pParam initial {@link #param}.
     * @param pMethodName method name to invoke inside hibernate session.
     * @param pCriteriaType criteria type.
     * @param class1 target Entity class.
     */
    public ReadObjectCallback1(final Map<String, Object> pParam, final String pMethodName,
            final CriteriaType pCriteriaType,
            final Class<? extends AbstractBean> class1) {
        super();
        this.param = pParam;
        this.methodName = pMethodName;
        this.criteriaType = pCriteriaType;
        this.targetClass = class1;
    }

    @SuppressWarnings(WarningType.UNCHECKED)
    @Override
    public T doInHibernate(final Session session) throws SQLException
    {
		session.enableFilter("patchFilter"); // Hibernate bug work around.
        final Criteria criteria = session.createCriteria(targetClass);
        final List<Criterion> critList = new ArrayList<Criterion>();

        buildCriteriaList(critList);
        if (!critList.isEmpty()) {
            Criterion andCriterion = critList.get(0);
            for (int i = 1; i < critList.size(); i++) {
                andCriterion = Restrictions.and(andCriterion, critList.get(i));
            }
            criteria.add(andCriterion);
        }
        try {
            return (T) criteria.getClass().getMethod(methodName, new Class[0])
                    .invoke(criteria, new Object[0]);
        } catch (final Exception e) {
            if (e.getCause() instanceof NonUniqueResultException) {
                throw new JournalException(e.getCause().getMessage(), e.getCause()); // NOPMD by r39 on 3/30/11 12:07 PM
            } else if (e.getCause() instanceof QueryException) {
                throw JournalException.wrapperException(new IllegalArgumentException(Messages.Error.IAE_INVALID_PROP
                        + param.keySet(), e
                        .getCause()));
            } else if (e.getCause() instanceof ClassCastException) {
                throw new IllegalArgumentException(Messages.Error.IAE_TYPE_MISMATCH, e.getCause()); // NOPMD by r39 on 3/30/11 12:07 PM
            } else {
                if (InvocationTargetException.class.equals(e.getClass())) {
                    final InvocationTargetException ite = (InvocationTargetException) e;
                    final String errorMessage = ite.getTargetException().getMessage();
                    throw new JournalException(errorMessage, ite);//NOPMD we want to wrap original stack trace.
                } else {
                    throw new JournalException(Messages.Error.SYSTEM_ERROR, e);
                }
            }
        }
    }

    /**
     * RTFC.
     *
     * @param critList Criteria list.
     */
    private void buildCriteriaList(final List<Criterion> critList) {
        Criterion criterion;
        for (final Object key : param.keySet()) {
            final Object value = param.get(key);
            switch (criteriaType) {
            case Like:
                criterion = Restrictions.like(key.toString(), value);
                break;
            case Ilike:
                criterion = Restrictions.ilike(key.toString(), value);
                break;
            default:
                criterion = Restrictions.eq(key.toString(), value);
            }
            critList.add(criterion);
        }
    }
}
